<!doctype html>
<html lang="cs">
	<head>
		<title>Nehostinný vesmír!</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<style>
			body {
				background: #000;
				color: #EEE;
				padding: 0;
				margin: 0;
				font-weight: bold;
				overflow: hidden;

				font-family: Monospace;
				font-size: 13px;
				text-align: left;
			}

			#info {
				position: absolute;
				top: 0px; left: 20%;
				width: 60%;
				padding: 5px;
				z-index: 100;
				background: rgba(0,0,0,0.5); 
			}

			#score {
				color: red;
				font-weight: bold;
			}

			a { color: green; }
			strong { color: red; }
		</style>

		<script src="http://mrdoob.github.com/three.js/build/Three.js"></script>

		<script src="http://mrdoob.github.com/three.js/examples/js/Detector.js"></script>
		<script src="http://mrdoob.github.com/three.js/examples/js/RequestAnimationFrame.js"></script>
		<script src="http://mrdoob.github.com/three.js/examples/js/Stats.js"></script>

	</head>

	<body>

		<div id="info">
			<p>
				Toto je <strong>nehostinný vesmír</strong>. Čti <a href="http://www.runtime.cz/2011/11/nehostinny-vesmir-readmetxt.html">readme.txt</a>.<br>
				<span id="instructions">V této hře jsi vesmír. Miř kometu pomocí &larr;, &rarr;, &uarr; a &darr;. Mezerníkem ji vypusť.</span><br/>
				Score: <span id="score">0</span>
			</p>
		</div>

		<script>

			// debug
			g_console = {
				dom : document.getElementById("console"),
				log : function(msg) {this.dom.innerHTML = msg}
			};
			
			// infobox
			g_info = {
				instructionDom : document.getElementById("instructions"),
				updateInstructions : function(msg) {this.instructionDom.innerHTML = msg},
				scoreDom : document.getElementById("score"),
				updateScore : function(score) {this.scoreDom.innerText = Math.floor(score)},
				validateScore : function() {this.scoreDom.style.color = "green"},
				invalidateScore : function() {this.scoreDom.style.color = "red"}
			}

			// start onload
			g_game = null;
			window.onload = function() {
				if ( !Detector.webgl ) {
					Detector.addGetWebGLMessage();
					return;
				}
				g_game = new Game();
				g_game.startMatch();
			}

			// Global level setups
			// textures from http://maps.jpl.nasa.gov/
			var g_levelSetups = {
				1: {
					"celestialBodies": {
						"sun" : {
							name: "Sun", radius: 100, rotationSpeed: 0, 
							primitive: "Sphere", material: "MeshDepthMaterial",
							mass: 1.9891e+3 //30
							},
						"venus" : { 
							name: "Venus", radius: 40, rotationSpeed: 2, orbitAround: "Sun", orbitRadius: 400, orbitSpeed: 0.2, angle: Math.PI, tilt: 0.2,
							primitive: "Sphere", material: "MeshPhongMaterial", texturePath: "images/ven0aaa2.jpeg",
							mass: 1e+2, //24
							},
						"earth" : { 
							name: "Earth", radius: 63, rotationSpeed: 0.5, orbitAround: "Sun", orbitRadius: 1000, orbitSpeed: 0.1, angle: 0, tilt: 0.41,
							primitive: "Sphere", material: "MeshPhongMaterial", texturePath: "images/earth_atmos_2048.jpg",
							mass: 5.9736e+2, //24
							playerTarget: true
							}, 
						"earthMoon" : {
							name: "EarthMoon", radius: 25, rotationSpeed: -Math.PI / 4, orbitAround: "Earth", orbitRadius: 200, orbitSpeed: 1, angle: 0, tilt: 0,
							primitive: "Sphere", material: "MeshPhongMaterial", texturePath: "images/moon_1024.jpg",
							mass: 7.349e+1 //22
							},
						"neptune" : { 
							name: "Neptune", radius: 80, rotationSpeed: 0.3, orbitAround: "Sun", orbitRadius: 1500, orbitSpeed: 0.05, angle: Math.PI*1.7, tilt: -0.6,
							primitive: "Sphere", material: "MeshPhongMaterial", texturePath: "images/nep0fds1.jpeg",
							mass: 9e+2 //24
							},
					},
					"rigidBodies" : {
						"comet" : {
							name: "Comet", radius: 5, x: -1000, z: -2000, vx: 200, vz: 0,
							primitive: "Sphere", material: "MeshDepthMaterial",
							mass: 1e+1,
							alive: false, playerControlled: true
						}
					}
				}
			}
			
			// Global physics constants
			var g_physics = {
				gravitationalConstant: 6.67384, //6.67384e-11,
				distanceBulgarianConstant: 1e-4,
				maxStartingForce: 1000,
				minStartingForce: 200,
				maxForceInOneStep: 20,//0.08,
				minDt: 0.001
			}
			
			
			// objects

			// Representation of the object in scene
			function WebGLObject(settings) {
				
				if (settings["material"] == "MeshDepthMaterial") {
					var material = new THREE.MeshDepthMaterial();//new THREE.MeshNormalMaterial( { shading: THREE.SmoothShading } );

					var geometry = new THREE.SphereGeometry( settings["radius"], 100, 50 );
					geometry.computeTangents();

					this.mesh = new THREE.Mesh( geometry, material );
					g_game.scene.add( this.mesh );
					
				} else if (("texturePath" in settings) && !("normalPath" in settings)) {
					var texture = THREE.ImageUtils.loadTexture( settings["texturePath"] );
					var material = new THREE.MeshPhongMaterial( { color: 0xffffff, map: texture } );
					
					// TODO: if geometry == sphere
					var geometry = new THREE.SphereGeometry( settings["radius"], 100, 50 );
					geometry.computeTangents();
					
					this.mesh = new THREE.Mesh( geometry, material );
					g_game.scene.add( this.mesh );
				}
			}

			// CelestialBody orbits around other celestial bodies in predefiner radii, is *not* affected by physics
			function CelestialBody(settings) {
				this.settings = settings;

				this.name = settings["name"];
				
				this.mass = settings["mass"];
				this.radius = settings["radius"];
				
				this.orbitSpeed = settings["orbitSpeed"];
				this.orbitRadius = settings["orbitRadius"];
				this.rotationSpeed = settings["rotationSpeed"];
				this.orbitAngle = settings["angle"];
				
				if ('playerTarget' in settings) {
					this.playerTarget = settings['playerTarget'];
				} else {
					this.playerTarget = false;					
				}
				
				this.webglObject = new WebGLObject(settings);
				
				if ('tilt' in settings) {
					this.tilt = settings["tilt"];
					this.webglObject.mesh.rotation.x += this.tilt;
				}
				
				this.position = new THREE.Vector3(0,0,0);
				
				this.updatePosition(0);
			}
			
			CelestialBody.prototype.updatePosition = function(dt) {
				
				this.webglObject.mesh.rotation.y += this.rotationSpeed * dt;
				
				if ("orbitAround" in this) {

					var orbitX = this.orbitAround.position.x;
					var orbitZ = this.orbitAround.position.z;

					this.orbitAngle += dt * this.orbitSpeed;

					this.position.x = Math.cos( this.orbitAngle ) * this.orbitRadius + orbitX;
					this.position.z = Math.sin( this.orbitAngle ) * this.orbitRadius + orbitZ;
				}
				
				this.webglObject.mesh.position = this.position;
			}
			
			// RigidBody is affected by physics.
			function RigidBody(settings) {
				this.settings = settings;

				this.name = settings["name"];
				
				this.mass = settings["mass"];
				this.radius = settings["radius"];
				
				if ('alive' in settings) {
					this.alive = settings['alive'];
				} else {
					this.alive = true;					
				}

				if ('playerControlled' in settings) {
					this.playerControlled = settings['playerControlled'];
					this.score = 0;
				} else {
					this.playerControlled = false;					
				}

				
				this.p_allBodies = null;
				
				if ('x' in settings && 'z' in settings) {
					this.startingPosition = new THREE.Vector3(settings['x'], 0, settings['z']);
				} else {
					this.startingPosition = new THREE.Vector3(500, 0, 500);
				}
				
				this.position = this.startingPosition.clone();

				if ('vx' in settings && 'vz' in settings) {
					this.velocity = new THREE.Vector3(settings['vx'], 0, settings['vz']);
				} else {
					this.velocity = new THREE.Vector3(0, 0, 0);
				}
				this.acceleration = new THREE.Vector3(0, 0, 0);
				
				this.webglObject = new WebGLObject(settings);
					
				this.updatePosition(0);
			}
			
			RigidBody.prototype.getGravitationalAccelerationTo = function(other) {
				if (this.mass == 0 || other.mass == 0) {
					return new THREE.Vector3(0, 0, 0)
				}
				var vectorToCurrent = other.position.clone().subSelf(this.position);
				var r_squared = vectorToCurrent.lengthSq();
				// var F = 6.67384e-11 * this.mass * other.mass / r_squared;
				var a = g_physics.gravitationalConstant * other.mass / (r_squared * g_physics.distanceBulgarianConstant);
				//console.log(a);
				return vectorToCurrent.divideScalar(Math.sqrt(r_squared)).multiplyScalar(a); // normalize then multiply by force
			}
			
			RigidBody.prototype.updateScore = function(accelerationLength, dt) {
				this.score += accelerationLength * dt - 200 * dt;
				if (this.score < 0) {
					this.score = 0;
				}
			}
			
			RigidBody.prototype.updatePosition = function(dt) {
				this.acceleration.set(0,0,0);
				
				for (key in this.p_allBodies) {
					var other = this.p_allBodies[key];
					if (this !== other) {
						this.acceleration.addSelf(this.getGravitationalAccelerationTo(other));
						if (this.position.clone().subSelf(other.position).length() < this.radius + other.radius) {
							if (this.playerControlled) {
								// we crashed
								this.alive = false; this.webglObject.mesh.visible = false;
								if (other.playerTarget) {
									g_info.validateScore();
									g_info.updateInstructions("<strong>Civilizace zničena!</strong> Doufám, že jsi patřičně hrdý na svou nehostinnost, jako!");
								} else {
									g_info.updateInstructions("Narazil jsi hezky, ale do <strong>špatného tělesa</strong>. Tvým cílem je Země. Resetuj mezerníkem.");
								}
							}
						}
					}
				}
				
				var accelerationLength = this.acceleration.length();
				
				if (accelerationLength * dt > g_physics.maxForceInOneStep && dt > g_physics.minDt) {
					// we're dealing with too strong forces per computation, need to have smaller steps
					var granularity = 2;
					var granular_dt = dt / granularity;
					//console.log("Previous dt = " + dt + " | Granular dt = " + granular_dt);

					
					for (var i = 0; i < granularity; i++) {
						this.updatePosition(granular_dt);
					}
				} else {
					if (this.playerControlled) {
						this.updateScore(accelerationLength, dt);
					}
					
					var starting_velocity = this.velocity.clone();
					
					// a = dv/dt --> v = u + a*dt
					this.velocity.addSelf(this.acceleration.multiplyScalar(dt));

					// dx = (u+v)/2 * dt
					this.position.addSelf(starting_velocity.addSelf(this.velocity).multiplyScalar(dt/2));

					this.webglObject.mesh.position = this.position;
				}				
			}
		
			function Level(levelNumber) {
				
				this.levelNumber = levelNumber;
				this.levelSetup = g_levelSetups[this.levelNumber];
				
				this.score = 0;
				
				this.celestialBodies = [];
				
				// current level objects
				for (celestialBodyName in this.levelSetup["celestialBodies"]) {
					var bodySettings = this.levelSetup["celestialBodies"][celestialBodyName];
					var newBody = new CelestialBody(bodySettings);
					
					// get object of the celestial body this one is orbiting around
					if ("orbitAround" in bodySettings) {
						for (i in this.celestialBodies) {
							if (this.celestialBodies[i].name == bodySettings["orbitAround"]) {
								newBody.orbitAround = this.celestialBodies[i];
							}
						}
					}
					
					// find out if this is the target
					if ("playerTarget" in bodySettings) {
						if (bodySettings['playerTarget']) {
							newBody.playerTarget = true;
							this.playerTargetBody = newBody;
						}
					}
					
					this.celestialBodies.push(newBody);
				}
				
				this.rigidBodies = [];
				this.playerControlledBody = null;
				
				// current level objects
				for (rigidBodyName in this.levelSetup["rigidBodies"]) {
					var newBody = new RigidBody(this.levelSetup["rigidBodies"][rigidBodyName]);
					this.rigidBodies.push(newBody);
					if (newBody.playerControlled) {
						this.playerControlledBody = newBody;
					}
				}
				
				this.allBodies = this.rigidBodies.concat(this.celestialBodies);
				
				// give context to all rigidbodies
				for (key in this.rigidBodies) {
					this.rigidBodies[key].p_allBodies = this.allBodies;
				}
				
				this.interactiveMode = true;
				this.startingPoint = this.playerControlledBody.position.clone();
				this.startingAngle = - Math.PI / 4;
				this.startingForce = 1.0;
				this.arrow = new Arrow();
				this.arrow.line.position = this.startingPoint.clone();
				
				var obj = this;
				window.addEventListener( 'keydown', function(e){obj.onKeyDown(e)}, false );
			}
			
			function Arrow(playerControlledBody) {
				this.vector = new THREE.Vector3(10, 0, 10);
				this.geometry = new THREE.Geometry();
				this.material = new THREE.LineBasicMaterial( { color: 0xffffff, opacity: 1, linewidth: 5 } );
				var path = [[0,0], [300,0], [200,-50], [200,50], [300,0]];
				
				for (var i = 0; i < path.length; i++) {
					var vec = new THREE.Vector3( path[i][0], 0, path[i][1]);
					this.geometry.vertices.push( new THREE.Vertex(vec) );
				}
				
				this.line = new THREE.Line(this.geometry, this.material, THREE.LineStrip);
				// this.line.scale.x = this.line.scale.y = this.line.scale.z = 10;
				// 				this.line.updateMatrix();
				g_game.scene.add(this.line);
			}
			
			Arrow.prototype.setRotation = function(angle) {
				this.line.rotation.y = angle;
			}
			
			Arrow.prototype.setForce = function(force) {
				this.line.scale.x = force;
			}
			
			Level.prototype.reset = function() {
				this.interactiveMode = true;
				this.playerControlledBody.position = this.playerControlledBody.startingPosition.clone();
				this.playerControlledBody.updatePosition(0);
				this.playerControlledBody.velocity.set(0, 0, 0);
				this.playerControlledBody.alive = false;
				this.playerControlledBody.webglObject.mesh.visible = true;
				this.playerControlledBody.score = 0;
				this.arrow.line.visible = true;
				g_info.invalidateScore();
				g_info.updateScore(0);
				g_info.updateInstructions("V této hře jsi vesmír. Miř kometu pomocí &larr;, &rarr;, &uarr; a &darr;. Mezerníkem ji vypusť.");
			}
			
			Level.prototype.shoot = function() {
				if (!this.interactiveMode) {
					this.reset();
				} else {
					this.interactiveMode = false;

					this.playerControlledBody.alive = true;
					this.playerControlledBody.webglObject.mesh.rotation.y = this.arrow.line.rotation.y;
					this.playerControlledBody.webglObject.mesh.visible = true;

					var trueStartingForce = g_physics.minStartingForce + (g_physics.maxStartingForce - g_physics.minStartingForce) * (this.startingForce - 0.5);

					this.playerControlledBody.velocity.x = Math.cos(this.startingAngle) * trueStartingForce;
					this.playerControlledBody.velocity.z = - Math.sin(this.startingAngle) * trueStartingForce;

					this.arrow.line.visible = false;
					g_info.updateInstructions("Sleduj score. Čím zajímavější trasa komety, tím lépe. Mezerníkem to můžeš zkusit znovu.");
				}
			}
			
			Level.prototype.onKeyDown = function ( event ) {

				switch( event.keyCode ) {

					case 38: /*up*/
					case 87: /*W*/ this.startingForce += 0.05; 
						if (this.startingForce > 1.5) {this.startingForce = 1.5}
					break;

					case 40: /*down*/
					case 83: /*S*/ this.startingForce -= 0.05; 
						if (this.startingForce < 0.5) {this.startingForce = 0.5}
					break;

					case 37: /*left*/
					case 65: /*A*/ this.startingAngle += 0.05; break;

					case 39: /*right*/
					case 68: /*D*/ this.startingAngle -= 0.05; break;
					
					case 32: /*space*/
					case 70: /*F*/ this.shoot(); break;
				}
			};
			
			Level.prototype.animate = function(dt) {
				for (currentKey in this.celestialBodies) {
					this.celestialBodies[currentKey].updatePosition(dt);
				}
				
				for (currentKey in this.rigidBodies) {
					var current = this.rigidBodies[currentKey];
					if (current.alive) {
						current.updatePosition(dt);
					}
				}
				
				if (this.interactiveMode) {
					this.arrow.setRotation(this.startingAngle);
					this.arrow.setForce(this.startingForce);
				} else {
					g_info.updateScore(this.playerControlledBody.score);
				}
			}
		
			function Match() {
				// a series of levels that make one game "match" - the sum of score gets the high score
				this.score = 0;
				this.startNewLevel(1);
			};
			
			Match.prototype.startNewLevel = function(levelNumber) {
				if (levelNumber !== undefined) {
					this.currentLevel = levelNumber;
				} else {
					this.currentLevel += 1;
				}
				
				this.level = new Level(this.currentLevel);
			}
		
			function Game() {
				// the main object - can only have one instance
				
				var height = window.innerHeight,
				width  = window.innerWidth,

				container, stats,

				camera, controls, scene, 
				geometry, meshPlanet, meshClouds, meshMoon,
				comet,
				dirLight, dirLight2, sunLight;


				this.originVector = new THREE.Vector3(0, 0, 0);
				
				height = window.innerHeight;
				width  = window.innerWidth;
				
				this.windowHalfX = height / 2;
				this.windowHalfY = height / 2;
				
				this.mouseX = 0;
				this.mouseY = 0;
				
				time = new Date().getTime();
				
				// globals
				this.playerName = "John Universe";
				this.highScore = {};
				
				// webgl, lighting, etc.
				
				var container = document.createElement( 'div' );
				document.body.appendChild( container );

				this.scene = new THREE.Scene();

				this.renderer = new THREE.WebGLRenderer( { clearAlpha: 1, clearColor: 0x000000 } );
				this.renderer.setSize( width, height );
				this.renderer.sortObjects = false;
				this.renderer.autoClear = false;

				container.appendChild( this.renderer.domElement );

				this.camera = new THREE.PerspectiveCamera( 25, width / height, 50, 1e7 );
				this.camera.position.y = 60 * 50;
				this.camera.position.x = - 60 * 50;
				this.camera.lookAt(new THREE.Vector3(0,0,0));

				dirLight = new THREE.DirectionalLight( 0xFFFFFF );
				dirLight.position.set( -1, 0, 1 ).normalize();
				this.scene.add( dirLight );
				
				dirLight2 = new THREE.DirectionalLight( 0x999999 );
				dirLight2.position.set( 1, 1, -1 ).normalize();
				this.scene.add( dirLight2 );
				
				sunLight = new THREE.PointLight( 0xFFEFC0, 2, 0 );
				this.scene.add( sunLight );
				
				
				// plane
				
				this.planeMesh = new THREE.Mesh( new THREE.PlaneGeometry( 63 * 200, 63 * 200, 8, 8 ), new THREE.MeshBasicMaterial( { color: 0x999999, opacity: 0.25, transparent: true, wireframe: true } ) );
				this.planeMesh.visible = true;
				this.planeMesh.rotation.x = 3 * Math.PI / 2;
				this.scene.add( this.planeMesh );
				
				
				// stars

				var i,
				vector,
				starsGeometry = new THREE.Geometry();

				for ( i = 0; i < 1500; i++ ) {

					vector = new THREE.Vector3( Math.random() * 2 - 1, Math.random() * 2 - 1, Math.random() * 2 - 1 );
					vector.multiplyScalar( 63 );

					starsGeometry.vertices.push( new THREE.Vertex( vector ) );

				}

				var stars,
				starsMaterials = [
					new THREE.ParticleBasicMaterial( { color: 0x555555, size: 1, sizeAttenuation: false } ),
					new THREE.ParticleBasicMaterial( { color: 0x555555, size: 0.5, sizeAttenuation: false } ),
					new THREE.ParticleBasicMaterial( { color: 0x333333, size: 1, sizeAttenuation: false } ),
					new THREE.ParticleBasicMaterial( { color: 0x3a3a3a, size: 0.5, sizeAttenuation: false } ),
					new THREE.ParticleBasicMaterial( { color: 0x1a1a1a, size: 1, sizeAttenuation: false } ),
					new THREE.ParticleBasicMaterial( { color: 0x1a1a1a, size: 0.5, sizeAttenuation: false } )
				];

				for ( i = 10; i < 30; i++ ) {

					stars = new THREE.ParticleSystem( starsGeometry, starsMaterials[ i % 6 ] );

					stars.rotation.x = Math.random() * 6;
					stars.rotation.y = Math.random() * 6;
					stars.rotation.z = Math.random() * 6;

					var s = i * 10;
					stars.scale.set( s, s, s );

					stars.matrixAutoUpdate = false;
					stars.updateMatrix();

					this.scene.add( stars );
				}
				
				this.stats = new Stats();
				this.stats.domElement.style.position = 'absolute';
				this.stats.domElement.style.top = '0px';
				this.stats.domElement.style.zIndex = 100;
				container.appendChild( this.stats.domElement );
				
				var obj = this;
				window.addEventListener( 'resize', function(){obj.onWindowResize()}, false );
				window.addEventListener( 'mousemove', function(e){obj.onDocumentMouseMove(e)}, false );
				
				// run main loop
				this.animate();
			};
			
			Game.prototype.startMatch = function() {
				this.match = new Match();
			}

			Game.prototype.onWindowResize = function( event ) {
				this.width = window.innerWidth;
				this.height = window.innerHeight;

				this.renderer.setSize( this.width, this.height );

				this.camera.aspect = this.width / this.height;
				this.camera.updateProjectionMatrix();

				this.camera.radius = ( this.width + this.height ) / 4;
			};
			
			Game.prototype.onDocumentMouseMove = function( event ) {
				this.mouseX = event.clientX - this.windowHalfX;
				this.mouseY = event.clientY - this.windowHalfY;
			}
			
			Game.prototype.animate = function() {
				
				// register hook for next animation frame
				var obj = this;
				requestAnimationFrame( function(){obj.animate()} );

				// celestial bodies update
				var t = new Date().getTime(),
				dt = Math.min(( t - time ) / 1000, 0.016); // min 60 fps
				time = t;

				if (this.match !== undefined && this.match.level !== null) {
					this.match.level.animate(dt);
				}
				
				this.render();
				this.stats.update();
				
			}
			
			Game.prototype.getCameraLookAtTarget = function(playerControlled, target) {
				var vectorToTarget = target.clone().subSelf(playerControlled);
				var distanceToTarget = vectorToTarget.length();
				var lookAtOffset = 1000;
				var distanceFromPlayerControlled = null;
				
				if (distanceToTarget < lookAtOffset) {
					return target;
				} else if (distanceToTarget < lookAtOffset * 2) {
					var leftoverDistanceRatio = (distanceToTarget - lookAtOffset) / lookAtOffset;
					distanceFromPlayerControlled = distanceToTarget - lookAtOffset * Math.pow(-leftoverDistanceRatio, 2);
				} else {
					distanceFromPlayerControlled = lookAtOffset;
				}
				
				var lookAtTarget = playerControlled.clone();
				lookAtTarget.addSelf(vectorToTarget.divideScalar(distanceToTarget).multiplyScalar(distanceFromPlayerControlled));
				return lookAtTarget;
			}
			
			Game.prototype.updateCamera = function() {
				this.camera.position.z += ( + this.mouseX - 2000 - this.camera.position.z ) * .05;
				this.camera.position.x += ( - this.mouseY - 4000 - this.camera.position.x ) * .05;
				
				if (this.match !== undefined && this.match.level !== null) {
					if (this.match.level.playerControlledBody !== undefined && this.match.level.playerTargetBody != undefined) {
						this.camera.lookAt(this.getCameraLookAtTarget(
													this.match.level.playerControlledBody.position,
													this.match.level.playerTargetBody.position	
											));
					}
					
				}
			}
			
			Game.prototype.render = function() {
				this.updateCamera();				

				this.renderer.clear();
				this.renderer.render( this.scene, this.camera );
			}

		</script>
	</body>
</html>
